package zom.zenjava.zendeploy.util;

import java.io.*;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class ArchiveUnzipper
{
    public static final int DEFAULT_BUFFER_SIZE = 1024;
    
    private int bufferSize;
    private File archive;
    private File directory;
    private ArchiveUnzipMonitor unzipMonitor;
    private boolean cancelled;

    public ArchiveUnzipper(File archive, File directory)
    {
        this(archive, directory, null, DEFAULT_BUFFER_SIZE);
    }

    public ArchiveUnzipper(File archive, File directory, ArchiveUnzipMonitor unzipMonitor)
    {
        this(archive, directory, unzipMonitor, DEFAULT_BUFFER_SIZE);
    }

    public ArchiveUnzipper(File archive, File directory, ArchiveUnzipMonitor unzipMonitor, int bufferSize)
    {
        this.archive = archive;
        this.directory = directory;
        this.unzipMonitor = unzipMonitor;
        this.bufferSize = bufferSize;
        this.cancelled = false;
    }

    public File getArchive()
    {
        return archive;
    }

    public File getDirectory()
    {
        return directory;
    }

    public int getBufferSize()
    {
        return bufferSize;
    }

    public boolean isCancelled()
    {
        return cancelled;
    }

    public ArchiveUnzipMonitor getUnzipMonitor()
    {
        return unzipMonitor;
    }

    public synchronized void cancel()
    {
        this.cancelled = true;
    }

    public void unzip() throws IOException
    {
        ZipFile zipFile = null;

        try
        {
            zipFile = new ZipFile(archive);
            update(null, 0, 0, 0, zipFile.size());
            
            checkCancelled();
            Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
            checkCancelled();

            byte data[] = new byte[bufferSize];
            int numEntriesProcessed = 0;
            while (zipEntries.hasMoreElements())
            {
                ZipEntry zipEntry = zipEntries.nextElement();
                update(zipEntry.getName(), 0, zipEntry.getSize(), numEntriesProcessed, zipFile.size());

                File file = new File(directory, zipEntry.getName());
                if (zipEntry.isDirectory())
                {
                    file.mkdirs();
                    update(zipEntry.getName(), 1, 1, numEntriesProcessed, zipFile.size());
                }
                else 
                {
                    BufferedOutputStream output = null;
                    InputStream zipInput = null;
                    try
                    {
                        file.getParentFile().mkdirs();
                        zipInput = zipFile.getInputStream(zipEntry);
                        output = new BufferedOutputStream(new FileOutputStream(file), bufferSize);

                        checkCancelled();

                        int count;
                        long bytesRead = 0;
                        while ((count = zipInput.read(data, 0, bufferSize)) != -1)
                        {
                            output.write(data, 0, count);
                            bytesRead += count;
                            update(zipEntry.getName(), bytesRead, zipEntry.getSize(), numEntriesProcessed, zipFile.size());
                            checkCancelled();
                        }
                        output.flush();
                        update(zipEntry.getName(), zipEntry.getSize(), zipEntry.getSize(), numEntriesProcessed, zipFile.size());
                        checkCancelled();
                    }
                    finally
                    {
                        if (zipInput != null)
                        {
                            zipInput.close();
                        }

                        if (output != null)
                        {
                            output.close();
                        }
                    }
                }
                checkCancelled();
                numEntriesProcessed++;
            }

            update(null, 0, 0, zipFile.size(), zipFile.size());
        }
        finally
        {
            if (zipFile != null)
            {
                zipFile.close();
            }
        }
    }

    private void checkCancelled() throws ArchiveUnzipCancelledException
    {
        synchronized (this)
        {
            if (cancelled)
            {
                throw new ArchiveUnzipCancelledException();
            }
        }
    }
    
    private void update(String currentFile,
                        long currentFileBytesReadSoFar,
                        long currentFileTotalBytes,
                        int filesUnzippedSoFar,
                        int totalFilesToUnzip)
    {
        if (unzipMonitor != null)
        {
            unzipMonitor.unzipUpdated(new ArchiveUnzipUpdate(this,
                    currentFile, currentFileBytesReadSoFar, currentFileTotalBytes, 
                    filesUnzippedSoFar, totalFilesToUnzip));
        }
    }
}
